#ifndef MEMSTORAGE_CPP_
#define MEMSTORAGE_CPP_

#include "memstorage.hpp"
#include <thread>
#include <string>
#include "filestorage.hpp"
#include "memstorage_exceptions.hpp"

#define LOGGING_INFO
#include "logger.h"

#include <sys/time.h>

//#define THREAD_SAFE

using std::thread;
using std::string;
using std::make_pair;
using std::bind;
using std::lock_guard;
using std::mutex;

MemStorage::MemStorage(size_t mem_size, size_t swap_size, const string& swap_path)	
	: 	swap_(new FileStorage(swap_size, swap_path)), 
		data_free_(swap_size), 
		total_allocated_(0), 
		data_size_(mem_size), 
		data_pos_(0)
	{
		data_ = new char[mem_size];
	}

DataChunk* MemStorage::alloc(size_t size)
{	
	if (data_free_ < size) {
		LOG_ERROR("no enough mem");
		throw MemAllocException(MemAllocException::E_NOMEM);
	}
	
	if (data_size_ < size) {
		LOG_ERROR("to big for storage");
		throw MemAllocException(MemAllocException::E_2BIG);
	}
	
	total_allocated_ += size;
	DataChunk* swap_chunk = swap_->alloc(size);
	DataChunk* mem_chunk = new DataChunk(this, size);

#ifdef THREAD_SAFE	
	lock_guard<mutex> lock(global_mutex_);
#endif
	
	appendChunkPair(mem_chunk, swap_chunk);
	chunks_all_.push_back(mem_chunk);
	data_free_ -= size;
	
	return mem_chunk;
}

void MemStorage::free(DataChunk* chunk)
{
#ifdef THREAD_SAFE	
	lock_guard<mutex> lock(global_mutex_);
#endif

	auto chunk_pair = getChunkPair(chunk);
	
	chunk_pair->second->free();
	// FIXME: Check find() != end()
	chunks_all_.erase(std::find(chunks_all_.begin(), chunks_all_.end(),	chunk_pair->first));//
	delete chunk_pair->first;
	
	removeChunkPair(chunk);

	data_free_ += chunk->size_;
}

char* MemStorage::get(DataChunk* chunk)
{

#ifdef THREAD_SAFE	
	lock_guard<mutex> lock(global_mutex_);
#endif

	auto chunk_pair = getChunkPair(chunk);
	
	if (data_size_ - data_pos_ < chunk->size_) {
		// FIXME: you mean upper_bound?
		auto hole = holes_.lower_bound(chunk->size_);
		if (hole == holes_.end()) {
			LOG_ERROR("too fragmented mem ");
			throw MemAllocException(MemAllocException::E_NOBUFS);
		}
		chunk_pair->first->data_ = hole->second;
		
		if (hole->first > chunk_pair->first->size_)
			holes_.insert(make_pair(hole->first - chunk_pair->first->size_, chunk_pair->first->data_ + chunk_pair->first->size_));
		
		holes_.erase(hole);
	} else {
		chunk_pair->first->data_ = data_ + data_pos_;
		data_pos_ += chunk_pair->first->size_;
	}

	if (chunk->touched_ != 1) {
		memcpy(chunk_pair->first->data_, chunk_pair->second->get(), chunk_pair->first->size_);
		chunk_pair->second->release();
	}
	
	return chunk_pair->first->data_; 
}

void MemStorage::release(DataChunk* chunk)
{

#ifdef THREAD_SAFE	
	lock_guard<mutex> lock(global_mutex_);
#endif
	if (chunk->locked_ == 1 && data_size_ - data_pos_ < data_size_ / 10) { //FIXME put optimisation here //not even not locked is nessesary to remove from buffer
		// FYI: +1 for previous comment
		auto chunk_pair = getChunkPair(chunk);
		
		auto swap_mem = chunk_pair->second->get();
		memcpy(swap_mem, chunk_pair->first->data_, chunk_pair->first->size_);
		chunk_pair->second->release();
		holes_.insert(make_pair(chunk_pair->first->size_, chunk_pair->first->data_));
		chunk->data_ = nullptr;
		chunk_pair->first->data_ = nullptr;
	}

}
MemStorage::~MemStorage()
{
	for (auto chunk_pair : chunks_)
		delete chunk_pair.first;
	delete[] data_;
	delete swap_;
	//LOG_INFO("end: <%lu>", total_allocated_);
}


#endif //MEMSTORAGE_CPP_
